const crypto = require('crypto');
const { globalAgent } = require('http');
const { platform } = require('os');
global.users = [];
global.messages = [];
global.sockets = new Map();

class WebSocket {

  constructor() {
    this.req = null;
    this.socket = null;
  }

  /**
   * 注册协议升级，监听数据变化
   */
  callback() {
    return (req, socket) => {
      const secWebsocketAccept = WebSocket.getSecWebSocketAccept(req.headers['sec-websocket-key']);
      const headers = [
        'HTTP/1.1 101 Switching Protocols',
        'Upgrade: websocket',
        'Connection: Upgrade',
        `Sec-WebSocket-Accept: ${secWebsocketAccept}`
      ]
      socket.write(headers.join('\r\n') + '\r\n\r\n', 'ascii');

      this.req = req;
      this.socket = socket;
      global.sockets.set(req.url.slice(1), socket);

      socket.on('data', data => {
        try {
          // 接收
          const payload = JSON.parse(WebSocket.decodeSocketFrame(data).payloadBuf.toString());
          if (payload.type) {
            switch (payload.type) {
              case 'connection':
                if (global.users.indexOf(payload.name) < 0) {
                  global.users.push(payload.name);
                }
                break;
              case 'sendMessage':
                global.messages.push(payload);
                break;
              default:
                const buf = WebSocket.encodeSocketFrame(JSON.stringify({ success: false, msg: '未知的 type 字段值' }));
                curSocket.write(buf);
                break;
            }

            for(let curSocket of global.sockets.values()) {
              const bufUsers = WebSocket.encodeSocketFrame(JSON.stringify({
                success: true,
                type: 'users',
                data: [...global.users],
                msg: ''
              }));
              curSocket.write(bufUsers);
              const bufMessages = WebSocket.encodeSocketFrame(JSON.stringify({
                success: true,
                type: 'message',
                data: [...global.messages],
                msg: ''
              }));
              curSocket.write(bufMessages);
            }
          } else {
            const buf = WebSocket.encodeSocketFrame(JSON.stringify({ success: false, msg: '缺少 type 字段' }));
            socket.write(buf);
          }
        } catch (e) {
          const buf = WebSocket.encodeSocketFrame(JSON.stringify({ success: false, msg: e.message }));
          socket.write(buf);
        }
        // 发送
        // const buf = WebSocket.encodeSocketFrame('你好，已接入 Websocket！');
        // curSocket.write(buf);
      });

      socket.on('error', err => {
        console.error(err);
      });
    }
  }

  /**
   * 计算 Sec-WebSocket-Accept 响应头
   * 这个返回头是根据客户端的请求头sec-websocket-key,加上全局唯一ID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)后使用sha1摘要后，再以base64格式输出。
   * (https://tools.ietf.org/html/rfc6455)
   */
  static getSecWebSocketAccept(secWebsocketKey) {
    return crypto.createHash('sha1')
    .update(`${secWebsocketKey}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
    .digest('base64');
  }

  // websocket还有frame的概念，接收到data就是一个frame，在这个框架里面有一定的结构(https://tools.ietf.org/html/rfc6455#section-5.2)
  /**
    第二三行重新加了个按字节和比特来计算的比例尺
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
                   1               2               3               4
     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-------+-+-------------+-------------------------------+
    |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
    |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
    |N|V|V|V|       |S|             |   (if payload len==126/127)   |
    | |1|2|3|       |K|             |                               |
    +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
    |     Extended payload length continued, if payload len == 127  |
    + - - - - - - - - - - - - - - - +-------------------------------+
    |                               |Masking-key, if MASK set to 1  |
    +-------------------------------+-------------------------------+
    | Masking-key (continued)       |          Payload Data         |
    +-------------------------------- - - - - - - - - - - - - - - - +
    :                     Payload Data continued ...                :
    + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    |                     Payload Data continued ...                |
    +---------------------------------------------------------------+
  */
  /**
  第1个字节的第1个比特是FIN的值，用来标识这个frame信息传递是否结束，1是结束
  第1个字节的第2-3个比特是RSV的值，用来标识这个frame信息传递是否结束
  第1个字节的第4-8个比特是opcode的值，来标记状态1是文本数据2是二进制数据8是请求关闭链接
  ---
  第2个字节的第1个比特是Mask的值，用来标识数据是否使用Masking-key来做异或解码
  第2个字节的第2-8个比特PayloadLen,
  代表数据长度，如果为126，则使用16位的扩展数据长
  代表数据长度，如果为127，则使用64位的扩展数据长度
  扩展长度使用大字端读取就好
 */
  static decodeSocketFrame(bufData) {
    let bufIndex = 0;
    const byte1 = bufData.readUInt8(bufIndex++).toString(2);
    const byte2 = bufData.readUInt8(bufIndex++).toString(2);
    const frame = {
      fin: parseInt(byte1.substring(0, 1), 2),
      // RSV 是保留字段暂不计算
      opcode: parseInt(byte1.substring(4, 8), 2),
      mask: parseInt(byte2.substring(0, 1), 2),
      payloadLen: parseInt(byte2.substring(1, 8), 2)
    }
    // 如果frame.payloadLen为126或127说明这个长度不够了，要使用扩展长度了
    // 如果frame.payloadLen为126，则使用Extended payload length同时为16/8字节数
    // 如果frame.payloadLen为127，则使用Extended payload length同时为64/8字节数
    // 注意payloadLen得长度单位是字节(bytes)而不是比特(bit)
    if (frame.payloadLen === 126) {
      frame.payloadLen = bufData.readUIntBE(bufIndex, 2);
      bufIndex += 2;
    } else if (frame.payloadLen === 127) {
      // 虽然是 8 字节，但是前四字节目前留空，因为 int 型是 4 字节不留空 int 会溢出
      bufIndex += 4;
      frame.payloadLen = bufData.readUIntBE(bufIndex, 4);
      bufIndex += 4;
    }
    if (frame.mask) {
      const payloadBufList = [];
      // maskingKey为4字节数据
      frame.maskingKey = [bufData[bufIndex++], bufData[bufIndex++], bufData[bufIndex++], bufData[bufIndex++]];
      for (let i = 0; i < frame.payloadLen; i += 1) {
        payloadBufList.push(bufData[bufIndex + i]^frame.maskingKey[i % 4]);
      }
      frame.payloadBuf = Buffer.from(payloadBufList);
    } else {
      frame.payloadBuf = bufData.slice(bufIndex, bufIndex + frame.payloadLen);
    }
    return frame;
  }

  // 上面的代码是 websocket 接收代码时的解码函数，对应的在发送数据要有一个编码函数
  static encodeSocketFrame(message) {
    message = String(message);
    let length = Buffer.byteLength(message);
    //数据的起始位置，如果数据长度16位也无法描述，则用64位，即8字节，如果16位能描述则用2字节，否则用第二个字节描述
    let index = 2 + (length > 65535 ? 8 : (length > 125 ? 2 : 0));
    //定义buffer，长度为描述字节长度 + message长度
    let buffer = Buffer.alloc(index + length);
    //第一个字节，fin位为1，opcode为1
    buffer[0] = 129;
    //因为是由服务端发至客户端，所以无需masked掩码
    if (length > 65535) {
      buffer[1] = 127;
      //长度超过65535的则由8个字节表示，因为4个字节能表达的长度为4294967295，已经完全够用，因此直接将前面4个字节置0
      buffer.writeUInt32BE(0, 2);
      buffer.writeUInt32BE(length, 6);
    } else if (length > 125) {
      buffer[1] = 126;
      //长度超过125的话就由2个字节表示
      buffer.writeUInt16BE(length, 2);
    } else {
      buffer[1] = length;
    }
    // 写入正文
    buffer.write(message, index);
  
    return buffer;
  }
}

module.exports = WebSocket;